package xml.xpath.parse;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import xml.xpath.annotation.XPathList;
import xml.xpath.annotation.XPathObject;
import xml.xpath.annotation.XPathString;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.List;

public class XmlXPathParser {

	public static <T> T toBean(Document document, Class<T> clazz) throws Exception {
		String xpath = "";
		boolean bl = clazz.isAnnotationPresent(XPathObject.class);
		if(bl) {
			XPathObject xPathObject = clazz.getAnnotation(XPathObject.class);
			xpath += xPathObject.value().trim();
		}
		return toBean(document, clazz, xpath);
	} 
	
	private static <T> T toBean(Document document, Class<T> clazz, String xpath) throws Exception {
		T t = clazz.getConstructor().newInstance();
		Field[] fields = clazz.getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			Field field = fields[i];
			if(field.isAnnotationPresent(XPathObject.class)) {
				XPathObject xPathObject = field.getAnnotation(XPathObject.class);
				String fieldXpath = "";
				if(xPathObject.value().trim().startsWith("/")) {
					fieldXpath = xpath + xPathObject.value().trim();
				}else {
					fieldXpath = xpath + "/" + xPathObject.value().trim();
				}
				field.setAccessible(true);
				field.set(t, toBean(document, field.getType(), fieldXpath));
			}else if(field.isAnnotationPresent(XPathList.class)) {
				//根据字段类型(包括泛型)判断 是否带有参数类型
				if(field.getGenericType() instanceof ParameterizedType){
					ParameterizedType pt = (ParameterizedType) field.getGenericType();
					Type type = pt.getActualTypeArguments()[0];
					if(type instanceof WildcardType) {	//如果泛型属于未定义类型 直接跳过
						continue;
					}
					Class<?> fieldClazz = (Class<?>)type;
					boolean bl = false;
					if(Element.class.equals(fieldClazz)) {
						bl = true;
					}
					List<Object> list = new ArrayList<>();
					XPathList xPathList = field.getAnnotation(XPathList.class);
					String fieldXpath = "";
					if(xPathList.value().trim().startsWith("/")) {
						fieldXpath = xpath + xPathList.value().trim();
					}else {
						fieldXpath = xpath + "/" + xPathList.value().trim();
					}
					@SuppressWarnings("unchecked")
					List<Node> nodes = document.selectNodes(fieldXpath);
					for (int j = 0; j < nodes.size(); j++) {
						if(bl) {
							list.add(nodes.get(j));
						}else {
							list.add(toBean(document, fieldClazz, fieldXpath + "[" + (j+1) + "]"));
						}
					}
					field.setAccessible(true);
					field.set(t, list);
				}
			}else if(field.isAnnotationPresent(XPathString.class)) {
				XPathString xPathString = field.getAnnotation(XPathString.class);
				String fieldXpath = "";
				if("".equals(xPathString.value().trim())) {
					fieldXpath = field.getName();
				}else {
					fieldXpath = xPathString.value().trim();
				}
				Node node = document.selectSingleNode(xpath);
				if(node == null) {
					continue;
				}
				String value = null;
				if(fieldXpath.startsWith("@")) {
					value = node.valueOf(fieldXpath);
				}else if(fieldXpath.equals("name")){
					value = node.getName();
				}else if(fieldXpath.equals("text")){
					value = node.getText();
				}else if(fieldXpath.equals("value")){
					value = node.getStringValue();
				}
				if(value != null) {
					field.setAccessible(true);
					field.set(t, value);
				}
			}
		}
		return t;
	} 
}
